/* ###################################### */
/* ###     Util Engine, by Kalith    ### */
/* ###################################### */
/*          AxisAlignedBox source         */
/*                                        */
/*                                        */

#include "Axisalignedbox.h"

#ifdef USING_OGRE
#include <OgreAxisAlignedBox.h>
#include <OgreRay.h>
#endif

using namespace std;

namespace Util
{
    AxisAlignedBox::AxisAlignedBox() :
        mMin_(Float::INFMINUS), mMax_(Float::INFPLUS)
    {

    }

    AxisAlignedBox::AxisAlignedBox(const Vector& mMin, const Vector& mMax) :
        mMin_(mMin), mMax_(mMax)
    {
    }

    AxisAlignedBox::~AxisAlignedBox()
    {
    }

    void AxisAlignedBox::SetMin( const Vector& mMin )
    {
        mMin_ = mMin;
    }

    void AxisAlignedBox::SetMax( const Vector& mMax )
    {
        mMax_ = mMax;
    }

    const Vector& AxisAlignedBox::GetMin() const
    {
        return mMin_;
    }

    const Vector& AxisAlignedBox::GetMax() const
    {
        return mMax_;
    }

    Vector AxisAlignedBox::operator[] (const UInt& uiIndex) const
    {
        if (uiIndex == 0)
            return mMin_;
        else if (uiIndex == 1)
            return Vector(mMax_.X(), mMin_.Y(), mMin_.Z());
        else if (uiIndex == 2)
            return Vector(mMax_.X(), mMin_.Y(), mMax_.Z());
        else if (uiIndex == 3)
            return Vector(mMin_.X(), mMin_.Y(), mMax_.Z());
        else if (uiIndex == 4)
            return Vector(mMin_.X(), mMax_.Y(), mMax_.Z());
        else if (uiIndex == 5)
            return Vector(mMin_.X(), mMax_.Y(), mMin_.Z());
        else if (uiIndex == 6)
            return Vector(mMax_.X(), mMax_.Y(), mMin_.Z());
        else if (uiIndex == 7)
            return mMax_;
        else
            return Vector::NaN;
    }

    Vector AxisAlignedBox::operator[] (const uint& uiIndex) const
    {
        if (uiIndex == 0)
            return mMin_;
        else if (uiIndex == 1)
            return Vector(mMax_.X(), mMin_.Y(), mMin_.Z());
        else if (uiIndex == 2)
            return Vector(mMax_.X(), mMin_.Y(), mMax_.Z());
        else if (uiIndex == 3)
            return Vector(mMin_.X(), mMin_.Y(), mMax_.Z());
        else if (uiIndex == 4)
            return Vector(mMin_.X(), mMax_.Y(), mMax_.Z());
        else if (uiIndex == 5)
            return Vector(mMin_.X(), mMax_.Y(), mMin_.Z());
        else if (uiIndex == 6)
            return Vector(mMax_.X(), mMax_.Y(), mMin_.Z());
        else if (uiIndex == 7)
            return mMax_;
        else
            return Vector::NaN;
    }

    AxisAlignedBox AxisAlignedBox::operator + ( const Vector& mAdd ) const
    {
        return AxisAlignedBox(mMin_ + mAdd, mMax_ + mAdd);
    }

    AxisAlignedBox AxisAlignedBox::operator - ( const Vector& mAdd ) const
    {
        return AxisAlignedBox(mMin_ - mAdd, mMax_ - mAdd);
    }

    void AxisAlignedBox::operator += ( const Vector& mAdd )
    {
        mMin_ += mAdd;
        mMax_ += mAdd;
    }

    void AxisAlignedBox::operator -= ( const Vector& mAdd )
    {
        mMin_ -= mAdd;
        mMax_ -= mAdd;
    }

    Bool AxisAlignedBox::Contains( const AxisAlignedBox& mBox ) const
    {
        if (IsInfinite() || mBox.IsInfinite())
            return true;

        if (mMax_.X() < mBox.mMin_.X())
            return false;
        if (mMax_.Y() < mBox.mMin_.Y())
            return false;
        if (mMax_.Z() < mBox.mMin_.Z())
            return false;

        if (mMin_.X() > mBox.mMax_.X())
            return false;
        if (mMin_.Y() > mBox.mMax_.Y())
            return false;
        if (mMin_.Z() > mBox.mMax_.Z())
            return false;

        return true;
    }

    Bool AxisAlignedBox::Contains( const Vector& mPoint ) const
    {
        return (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) &&
                mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()) &&
                mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()));
    }

    void AxisAlignedBox::Include( const Vector& mPoint )
    {
        mMax_.X() = Float::Max(mMax_.X(), mPoint.X());
        mMax_.Y() = Float::Max(mMax_.Y(), mPoint.Y());
        mMax_.Z() = Float::Max(mMax_.Z(), mPoint.Z());

        mMin_.X() = Float::Min(mMin_.X(), mPoint.X());
        mMin_.Y() = Float::Min(mMin_.Y(), mPoint.Y());
        mMin_.Z() = Float::Min(mMin_.Z(), mPoint.Z());
    }

    Bool AxisAlignedBox::IsInfinite() const
    {
        return mMin_.IsInfinite() || mMax_.IsInfinite();
    }

    Bool AxisAlignedBox::GetRayIntersection( const Vector& mRayOrigin, const Vector& mRayDirection, Vector& mIntersection ) const
    {
        if (Contains(mRayOrigin))
        {
            mIntersection = mRayOrigin;
            return true;
        }

        // Check collision with each face of the bounding box (the 3 closest)
        Float fLowestT = Float::INFPLUS;

        // X
        if (mRayOrigin.X() <= mMin_.X() && mRayDirection.X() > 0.0f)
        {
            Float fT = (mMin_.X() - mRayOrigin.X())/mRayDirection.X();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }
        if (mRayOrigin.X() >= mMax_.X() && mRayDirection.X() < 0.0f)
        {
            Float fT = (mMax_.X() - mRayOrigin.X())/mRayDirection.X();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }

        // Y
        if (mRayOrigin.Y() <= mMin_.Y() && mRayDirection.Y() > 0.0f)
        {
            Float fT = (mMin_.Y() - mRayOrigin.Y())/mRayDirection.Y();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }
        if (mRayOrigin.Y() >= mMax_.Y() && mRayDirection.Y() < 0.0f)
        {
            Float fT = (mMax_.Y() - mRayOrigin.Y())/mRayDirection.Y();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }

        // Z
        if (mRayOrigin.Z() <= mMin_.Z() && mRayDirection.Z() > 0.0f)
        {
            Float fT = (mMin_.Z() - mRayOrigin.Z())/mRayDirection.Z();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }
        if (mRayOrigin.Z() >= mMax_.Z() && mRayDirection.Z() < 0.0f)
        {
            Float fT = (mMax_.Z() - mRayOrigin.Z())/mRayDirection.Z();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                }
            }
        }

        return fLowestT.IsValid();
    }

    Bool AxisAlignedBox::GetRayIntersection( const Vector& mRayOrigin, const Vector& mRayDirection, Vector& mIntersection, Face& mFace ) const
    {
        if (Contains(mRayOrigin))
        {
            mIntersection = mRayOrigin;
            return true;
        }

        // Check collision with each face of the bounding box (the 3 closest)
        Float fLowestT = Float::INFPLUS;

        // X
        if (mRayOrigin.X() <= mMin_.X() && mRayDirection.X() > 0.0f)
        {
            Float fT = (mMin_.X() - mRayOrigin.X())/mRayDirection.X();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = LEFT;
                }
            }
        }
        if (mRayOrigin.X() >= mMax_.X() && mRayDirection.X() < 0.0f)
        {
            Float fT = (mMax_.X() - mRayOrigin.X())/mRayDirection.X();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = RIGHT;
                }
            }
        }

        // Y
        if (mRayOrigin.Y() <= mMin_.Y() && mRayDirection.Y() > 0.0f)
        {
            Float fT = (mMin_.Y() - mRayOrigin.Y())/mRayDirection.Y();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = BOTTOM;
                }
            }
        }
        if (mRayOrigin.Y() >= mMax_.Y() && mRayDirection.Y() < 0.0f)
        {
            Float fT = (mMax_.Y() - mRayOrigin.Y())/mRayDirection.Y();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Z().IsInRange(mMin_.Z(), mMax_.Z()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = TOP;
                }
            }
        }

        // Z
        if (mRayOrigin.Z() <= mMin_.Z() && mRayDirection.Z() > 0.0f)
        {
            Float fT = (mMin_.Z() - mRayOrigin.Z())/mRayDirection.Z();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = FRONT;
                }
            }
        }
        if (mRayOrigin.Z() >= mMax_.Z() && mRayDirection.Z() < 0.0f)
        {
            Float fT = (mMax_.Z() - mRayOrigin.Z())/mRayDirection.Z();
            Vector mPoint = mRayOrigin + mRayDirection*fT;
            if (mPoint.X().IsInRange(mMin_.X(), mMax_.X()) && mPoint.Y().IsInRange(mMin_.Y(), mMax_.Y()))
            {
                if (fT < fLowestT)
                {
                    fLowestT = fT;
                    mIntersection = mPoint;
                    mFace = BACK;
                }
            }
        }

        return fLowestT.IsValid();
    }

    #ifdef USING_OGRE
    Ogre::AxisAlignedBox AxisAlignedBox::UtilToOgre(const AxisAlignedBox& mBox)
    {
        if (mBox.IsInfinite())
            return Ogre::AxisAlignedBox(Ogre::AxisAlignedBox::EXTENT_INFINITE);
        else
            return Ogre::AxisAlignedBox(Vector::UtilToOgre(mBox.GetMin()), Vector::UtilToOgre(mBox.GetMax()));
    }

    AxisAlignedBox AxisAlignedBox::OgreToUtil(const Ogre::AxisAlignedBox& mBox)
    {
        if (mBox.isInfinite())
            return AxisAlignedBox();
        else
            return AxisAlignedBox(Vector::OgreToUtil(mBox.getMinimum()), Vector::OgreToUtil(mBox.getMaximum()));
    }
    #endif

    Str::string StringConverter<string_element, AxisAlignedBox>::Convert( const AxisAlignedBox& mBox )
    {
        return StringConverter<string_element, Str>::Convert("(min : ")+StringConverter<string_element, Vector>::Convert(mBox.GetMin())+
              StringConverter<string_element, Str>::Convert(", max : ")+StringConverter<string_element, Vector>::Convert(mBox.GetMax())+
			  StringConverter<string_element, Str>::Convert(")");
    }
}
